/* Copyright 2019 Franck Jullien
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libxml/parser.h>

#include "orlib2ki.h"
#include "draw.h"
#include "helpers.h"

#define VERSION	"v0.1"

void print_usage(void)
{
	printf("Usage: orlib2ki [-v] -i inputfile [-o outputfile] [-g grid_scale] [-t text_scale] [-s text_size]\n");
	printf("       orlib2ki -h\n");
}

int main(int argc, char *argv[]) 
{
	xmlNodePtr cur, package, physpart, tmp, normal_view_child, libpart;
	xmlNodePtr libpart_child, valueProp;
	struct def component;
	struct field f0, f1;
	xmlDocPtr doc;
	FILE *fp;
	int phys_count;
	int part_index;
	int i;
	char orientation;
	int size;
	char *p;
	int package_count = 0;

	int opt;
	char ifile[128] = {0};
	char ofile[128] = {0};
	int verbose = 0;
	float grid_scale = 7.5;
	float text_scale = 3;
	int text_size = 35;

	if (argc == 1) {
		print_usage();
		return EXIT_FAILURE;
	}

	while ((opt = getopt(argc, argv, "i:o:hvg:t:s:")) != -1) {
		switch(opt) {
		case 'i':
			strcpy(ifile, optarg);
			break;
		case 'o':
			strcpy(ofile, optarg);
			break;
		case 'v':
			verbose = 1;
			break;
		case 'g':
			sscanf(optarg, "%f", &grid_scale);
			break;
		case 't':
			sscanf(optarg, "%f", &text_scale);
			break;
		case 's':
			sscanf(optarg, "%u", &text_size);
			break;
		case 'h':
		case ':':
		case '?':
			print_usage();
			return EXIT_FAILURE;
			break;
		}
	}

	printf("\nORLIB2KI version %s by Franck Jullien\n", VERSION);

	xmlKeepBlanksDefault(0);

	doc = xmlReadFile(ifile, NULL, XML_PARSE_HUGE);
	if (doc == NULL) {
		fprintf(stderr, "Invalid input file\n");
		return EXIT_FAILURE;
	}

	cur = xmlDocGetRootElement(doc);
	if (cur == NULL) {
		fprintf(stderr, "Empty XML document\n");
		xmlFreeDoc(doc);
		return EXIT_FAILURE;
	}

	if (!ofile[0]) {
		strcpy(ofile, ifile);
		p = strrchr(ofile, '.');
		if (p)
			strcpy(p, ".lib");
		else
			strcat(ofile, ".lib");
	}

	fp = fopen(ofile, "w");

	fprintf(fp, "EESchema-LIBRARY Version 2.4\n\n");

	fprintf(fp, "# **** This file is automatically generated by orlib2ki %s\n", VERSION);
	fprintf(fp, "# **** https://github.com/fjullien/orlib2ki\n");

	cur = cur->xmlChildrenNode;
	package = find_next_node(cur, "Package");

	while (package) {

		package_count++;

		tmp = package->xmlChildrenNode;
		tmp = find_next_node(tmp, "Defn");

		get_defn_string(tmp, "refdesPrefix", component.refdes);
		get_defn_string(tmp, "name", component.name);
		get_defn_string(tmp, "pcbFootprint", component.footprint);

		if (verbose)
			printf("[CONV]  %s [", component.name);

		string_replace_char(component.name, ' ', '_');

		if (verbose)
			printf("%s]\n", component.name);

		component.units_locked = get_defn_int_val(tmp, "isHomogeneous");

		/* Count number of LibPart nodes in this Package */
		tmp = package->xmlChildrenNode;
		tmp = find_next_node(tmp, "LibPart");
		i = 0;

		while (tmp) {
			tmp = find_next_node(tmp, "LibPart");
			if (tmp)
				tmp = tmp->next;
			i++;
		}

		component.part_count = i;

		/* Count number of PhysicalPart nodes in this LibPart */
		if (i == 1) {
			tmp = package->xmlChildrenNode;
			tmp = find_next_node(tmp, "LibPart");
			phys_count = 0;

			physpart = tmp->xmlChildrenNode;
			while (physpart) {
				physpart = find_next_node(physpart, "PhysicalPart");
				physpart = physpart->next;
				phys_count++;
			}

			i = phys_count;
		}

		component.unit_count = i;

		/* Get first LibPart node of this Package */
		tmp = package->xmlChildrenNode;
		tmp = find_next_node(tmp, "LibPart");

		/* Get first normal_view_child node of this LibPart */
		tmp = tmp->xmlChildrenNode;
		tmp = find_next_node(tmp, "NormalView");

		/* Get IsPinNumbersVisible node of this normal_view_child */
		normal_view_child = tmp->xmlChildrenNode;

		tmp = find_next_node(normal_view_child, "IsPinNumbersVisible");
		tmp = tmp->xmlChildrenNode;
		tmp = find_next_node(tmp, "Defn");

		component.draw_pinnumber = get_defn_int_val(tmp, "val");


		tmp = find_next_node(normal_view_child, "PartValue");
		get_defn_string(tmp->xmlChildrenNode, "name", component.value);

		/* Get IsPinNumbersVisible node of this normal_view_child */
		tmp = find_next_node(normal_view_child, "IsPinNamesVisible");
		tmp = tmp->xmlChildrenNode;
		tmp = find_next_node(tmp, "Defn");

		component.draw_pinname = get_defn_int_val(tmp, "val");

		tmp = find_next_node(normal_view_child, "SymbolDisplayProp");
		if (!tmp) {
			if (verbose)
				printf("[WARN]\t%s has no refdes\n", component.name);
			package = find_next_node(package->next, "Package");
			continue;
		}

		fprintf(fp, "\n#***********************************************\n");
		fprintf(fp, "#* %s\n", component.name);
		fprintf(fp, "#***********************************************\n\n");

		fprintf(fp, "DEF %s %s 0 20 %s %s %d %s N\n", component.name,
							      component.refdes,
							      component.draw_pinnumber ? "Y" : "N",
							      component.draw_pinname ? "Y" : "N",
							      component.unit_count,
						 	      component.units_locked ? "F" : "L");

		/*****************************************************
		 *
		 *          COMPUTE F0
		 *
		 *****************************************************
		 */

		tmp = tmp->xmlChildrenNode;
		tmp = find_next_node(tmp, "Defn");

		f0.x = get_defn_int_val(tmp, "locX") * grid_scale;
		f0.y = get_defn_int_val(tmp, "locY") * -grid_scale;

		f0.rotation = get_defn_int_val(tmp, "rotation");

		if (f0.rotation == 0) {
			orientation = 'H';
			size = text_size;
		} else if (f0.rotation == 2) {
			orientation = 'H';
			size = -text_size;
		} else if (f0.rotation == 1) {
			orientation = 'V';
			size = text_size;
		} else if (f0.rotation == 3) {
			orientation = 'V';
			size = -text_size;
		}

		tmp = find_next_node(normal_view_child, "SymbolDisplayProp");
		tmp = tmp->xmlChildrenNode;
		tmp = find_next_node(tmp, "PropFont");
		tmp = tmp->xmlChildrenNode;
		tmp = find_next_node(tmp, "Defn");

		f0.italic = get_defn_int_val(tmp, "italic") ? 'I' : 'N';
		f0.bold = get_defn_int_val(tmp, "weight") > 400 ? 'B' : 'N';

		fprintf(fp, "F0 \"%s\" %d %d %d %c V L T%c%c \n", component.refdes,
								    f0.x,
								    f0.y - 10,
								    size,
								    orientation,
								    f0.italic,
								    f0.bold);

		/*****************************************************
		 *
		 *          COMPUTE F1/F2/F3
		 *
		 *****************************************************
		 */

		tmp = find_next_node(normal_view_child, "SymbolDisplayProp");

		/* We are looking for "Value" properties */
		valueProp = find_next_node(tmp->next, "SymbolDisplayProp");
		if (valueProp) {
			tmp = valueProp->xmlChildrenNode;
			tmp = find_next_node(tmp, "Defn");

			component.val_rot = get_defn_int_val(tmp, "rotation");
			component.value_x = get_defn_int_val(tmp, "locX") * grid_scale;
			component.value_y = get_defn_int_val(tmp, "locY") * -grid_scale;

			tmp = valueProp->xmlChildrenNode;
			tmp = find_next_node(tmp, "PropFont");

			tmp = tmp->xmlChildrenNode;
			tmp = find_next_node(tmp, "Defn");

			f1.italic = get_defn_int_val(tmp, "italic") ? 'I' : 'N';
			f1.bold = get_defn_int_val(tmp, "weight") > 400 ? 'B' : 'N';

			component.has_value = 1;
		} else {
			if (verbose)
				printf("[WARN]\t%s has no value\n", component.name);
			component.has_value = 0;
		}

		tmp = find_next_node(normal_view_child, "SymbolBBox");
		tmp = tmp->xmlChildrenNode;
		tmp = find_next_node(tmp, "Defn");

		component.box_right_x = get_defn_int_val(tmp, "x2") * grid_scale;
		component.box_right_y = get_defn_int_val(tmp, "y2")* -grid_scale;

		fprintf(fp, "F1 \"%s\" %d %d 35 H I L CNN \n", component.name,
							       component.box_right_x,
							       component.box_right_y);

		fprintf(fp, "F2 \"%s\" %d %d %d H I L CNN\n", component.footprint,
							      component.box_right_x,
							      component.box_right_y - text_size - 10,
							      text_size);

		if (component.val_rot == 0) {
			orientation = 'H';
			size = text_size;
		} else if (component.val_rot == 2) {
			orientation = 'H';
			size = -text_size;
		} else if (component.val_rot == 1) {
			orientation = 'V';
			size = text_size;
		} else if (component.val_rot == 3) {
			orientation = 'V';
			size = -text_size;
		}

		if (component.has_value) {
			fprintf(fp, "F4 \"%s\" %d %d %d %c V L T%c%c \n", component.value,
									  component.value_x,
									  component.value_y - 10,
									  size,
									  orientation,
									  f1.italic,
									  f1.bold);
		}

		/* Get first LibPart node of this Package */
		tmp = package->xmlChildrenNode;
		libpart = find_next_node(tmp, "LibPart");
		part_index = 1;

		component.nb_phys_part = get_physical_part_number(libpart->xmlChildrenNode);

		do_user_prop(text_size, libpart, component, fp);

		fprintf(fp, "DRAW\n");

		while (libpart) {
			tmp = libpart->xmlChildrenNode;
			libpart_child = find_next_node(tmp, "NormalView");
			normal_view_child = libpart_child->xmlChildrenNode;

			tmp = find_next_node(normal_view_child, "Rect");
			while (tmp) {
				do_rectangle(grid_scale, component.part_count == 1 ? 0 : part_index, tmp, fp);
				tmp = find_next_node(tmp->next, "Rect");
			}

			tmp = find_next_node(normal_view_child, "Ellipse");
			while (tmp) {
				do_ellipse(grid_scale, component.part_count == 1 ? 0 : part_index, tmp, fp);
				tmp = find_next_node(tmp->next, "Ellipse");
			}

			tmp = find_next_node(normal_view_child, "Line");
			while (tmp) {
				do_line(grid_scale, component.part_count == 1 ? 0 : part_index, tmp, fp);
				tmp = find_next_node(tmp->next, "Line");
			}

			tmp = find_next_node(normal_view_child, "Arc");
			while (tmp) {
				do_arc(grid_scale, component.part_count == 1 ? 0 : part_index, tmp, fp);
				tmp = find_next_node(tmp->next, "Arc");
			}

			tmp = find_next_node(normal_view_child, "CommentText");
			while (tmp) {
				do_text(text_scale, grid_scale, component.part_count == 1 ? 0 : part_index, tmp, fp);
				tmp = find_next_node(tmp->next, "CommentText");
			}

			tmp = find_next_node(normal_view_child, "Polygon");
			while (tmp) {
				do_polygon(grid_scale, component.part_count == 1 ? 0 : part_index, tmp, fp);
				tmp = find_next_node(tmp->next, "Polygon");
			}

			i = 1;
			tmp = find_next_node(normal_view_child, "SymbolPinScalar");
			while (tmp) {
				struct pin pin;
				do_pin(grid_scale, tmp, &pin);
				if ((component.units_locked == 1) && (component.nb_phys_part == 1)) {
					do_physical(text_size, libpart_child, &pin, fp, 0);
				} else if ((component.units_locked == 1) && (component.nb_phys_part > 1)) {
					do_physical(text_size, libpart_child, &pin, fp, 1);
				} else if (component.units_locked == 0) {
					do_physical(text_size, libpart_child, &pin, fp, part_index);
				}
				i++;
				tmp = find_next_node(tmp->next, "SymbolPinScalar");
			}

			libpart = find_next_node(libpart->next, "LibPart");
			part_index++;
		}

		package = find_next_node(package->next, "Package");

		fprintf(fp, "ENDDRAW\nENDDEF\n");
	}

	printf("%d parts converted in %s\n\n", package_count, ofile);

	xmlFreeDoc(doc);
	fclose(fp);

	return EXIT_SUCCESS;
}
